package net.qiqb.core.executing;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import lombok.extern.slf4j.Slf4j;
import net.qiqb.core.domain.DomainUtil;
import net.qiqb.core.domain.annotation.*;
import net.qiqb.core.domain.definition.AggregateRootDefine;
import net.qiqb.core.domain.definition.DomainDefine;
import net.qiqb.core.domain.definition.DomainDefineRegistrar;
import net.qiqb.core.domain.event.DomainEvent;
import net.qiqb.core.domain.event.DomainEventPersistence;
import net.qiqb.core.domain.event.sender.DomainEventSender;
import net.qiqb.core.domain.log.AggregateChangesLogger;
import net.qiqb.core.domain.persist.annotation.PersistOperation;
import net.qiqb.core.domain.persist.annotation.Transaction;
import net.qiqb.core.executing.annotation.AggregateObjectPostProcessor;
import net.qiqb.core.executing.definition.AggregateObjectPostProcessorRegistrar;
import net.qiqb.core.executing.definition.CommandDefine;
import net.qiqb.core.optional.commands.DeleteCommand;
import org.javers.core.Javers;
import org.javers.core.JaversBuilder;
import org.javers.core.diff.Change;
import org.javers.core.diff.Diff;
import org.javers.core.diff.changetype.ValueChange;
import org.javers.core.metamodel.clazz.EntityDefinitionBuilder;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

/**
 * 标准聚合命令执行过程
 */
@Slf4j
public class StandardAggregateExecutor {

    private final DomainDefineRegistrar domainDefineRegistrar;

    private final DomainEventPersistence domainEventPersistence;

    private Transaction transaction;

    private final AggregateChangesLogger aggregateChangesLogger;

    private final DomainEventSender domainEventSender;

    private final AggregateObjectPostProcessorRegistrar aggregateObjectPostProcessorRegistrar;

    public StandardAggregateExecutor(DomainDefineRegistrar domainDefineRegistrar, DomainEventPersistence domainEventPersistence
            , Transaction transaction, AggregateChangesLogger aggregateChangesLogger, DomainEventSender domainEventSender, AggregateObjectPostProcessorRegistrar aggregateObjectPostProcessorRegistrar) {

        this.domainDefineRegistrar = domainDefineRegistrar;
        this.domainEventPersistence = domainEventPersistence;
        this.transaction = transaction;
        this.aggregateChangesLogger = aggregateChangesLogger;
        this.domainEventSender = domainEventSender;
        this.aggregateObjectPostProcessorRegistrar = aggregateObjectPostProcessorRegistrar;
    }

    public void execute(Object... commandObjs) {
        ExecutingContext parent = ExecutingContextHolder.current();
        // 将命令转化为聚合根命令
        final AggregateCommand aggregateCommand = coverAggregateCommand(commandObjs);
        final AggregateRootDefine aggregateRootDefine = (AggregateRootDefine) aggregateCommand.getAggregateRootCommand().getCommandDefine().getDomainDefine();
        final ExecutingContext currentExecutingContext = new ExecutingContext(parent);
        ExecutingContextHolder.setCurrent(currentExecutingContext);
        try {
            // 获取领域对象
            Object aggregateRootObj = obtainAggregateRootObj(aggregateCommand);

            // 保存快照
            Object aggregateRootSnapshot = null;
            final Object aggregateRootId = lookupAggregateId(aggregateRootObj);
            if (!ExecutingContextHolder.current().isNewEntityId(aggregateRootId)) {
                aggregateRootSnapshot = ObjectUtil.clone(aggregateRootObj);
            }
            // 处理业务逻辑
            final Class<?> aggregateRootClass = handBusiness(aggregateCommand, aggregateRootObj);
            if (transaction == null) {
                transaction = PersistOperation::operate;
            }
            Javers javers = JaversBuilder.javers()
                    .registerEntity(EntityDefinitionBuilder
                            .entityDefinition(aggregateRootClass)
                            .withIdPropertyName("id")
                            .build()
                    )
                    .build();
            final Diff compare = javers.compare(aggregateRootSnapshot, aggregateRootObj);
            // 处理完业务之后，获取所有businessId 比较一下
            if (compare.hasChanges()) {
                //
                final List<Field> businessIdFields = DomainUtil.getBusinessIdFields(aggregateRootClass);
                for (Change change : compare.getChanges()) {
                    if (change instanceof ValueChange valueChange) {
                        final String propertyName = valueChange.getPropertyName();
                        final Optional<Field> matchedField = businessIdFields.stream().filter(field -> field.getName().equals(propertyName)).findFirst();
                        if (matchedField.isPresent()) {
                            final Field field = matchedField.get();
                            final AggregateRootLoader<Object, Object> aggregateRootLoader = aggregateRootDefine.getAggregateRootLoader(propertyName);
                            if (aggregateRootLoader != null) {
                                // 修改后的值去加载一遍，如果存在，就报错
                                final Object fieldValue = ReflectUtil.getFieldValue(aggregateRootObj, field);
                                Object other = aggregateRootLoader.loadAggregateRoot(fieldValue);
                                if (other != null) {
                                    throw new IllegalStateException("business id " + field.getName() + " 验证失败：已经存在" + fieldValue);
                                }
                            }
                        }
                    }
                }
            }
            // 持久化
            final Object oldAggregateRootObj = aggregateRootSnapshot;
            transaction.commit(() -> persistAggregate(aggregateRootDefine, aggregateRootObj, oldAggregateRootObj));
            // 触发事件
            try {
                final List<DomainEvent> domainEvents = ExecutingContextHolder.current().listEvents();
                for (DomainEvent domainEvent : domainEvents) {
                    domainEventSender.sending(domainEvent);
                }

            } catch (Exception e) {
                log.info("发送领域事件失败：\n{}", e.toString());
            }
            // 记录日志
            try {

                if (aggregateChangesLogger != null) {
                    aggregateChangesLogger.logging(aggregateRootId.toString(), compare.toString());
                }
                log.info("日志记录：\n{}", compare.toString());
            } catch (Exception e) {
                log.error("记录日志异常：{}", e.getMessage());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            ExecutingContextHolder.removeCurrent();
        }
    }

    private void checkBusinessIdUniqueness() {

    }

    private void persistAggregate(AggregateRootDefine aggregateRootDefine, Object aggregateRootObj, Object aggregateRootSnapshot) {
        AggregatePersistence<Object> aggregatePersistence = aggregateRootDefine.getAggregatePersistence();
        if (aggregatePersistence != null) {
            aggregatePersistence.persistAggregate(aggregateRootObj, aggregateRootSnapshot);
        }
        // 额外持久化
        final ExtendedAggregatePersistence<Object> extendedAggregatePersistence = aggregateRootDefine.getExtendedAggregatePersistence();
        if (extendedAggregatePersistence != null) {
            extendedAggregatePersistence.persistAggregate(aggregateRootObj, aggregateRootSnapshot);
        }

        if (aggregateRootObj instanceof ExpandableAggregateRoot<?> expandableAggregateRootObj) {

            final List<Object> domainObjs = expandableAggregateRootObj.getDomainObjs();
            for (Object domainObj : domainObjs) {
                final AttachedAggregatePersistence<Object, Object> matchedAttachedPersistence = aggregateRootDefine.getAttachedAggregatePersistence(domainObj.getClass());
                if (matchedAttachedPersistence != null) {
                    final AttachedAggregatePersistentObj<Object, Object> newObj = new AttachedAggregatePersistentObj<>(aggregateRootObj, domainObj);
                    final AttachedAggregatePersistentObj<Object, Object> oldObj = new AttachedAggregatePersistentObj<>(aggregateRootSnapshot, domainObj);
                    matchedAttachedPersistence.persistAggregate(newObj, oldObj);
                }
            }
        }
        // 持久化领域事件 Persist domain events
        final List<DomainEvent> domainEvents = ExecutingContextHolder.current().listEvents();
        if (!domainEvents.isEmpty()) {
            //
            if (domainEventPersistence != null) {
                domainEventPersistence.save(domainEvents);
            }
        }
    }

    private static Class<?> handBusiness(AggregateCommand aggregateCommand, Object aggregateRootObj) {

        final Object commandObj = aggregateCommand.getAggregateRootCommand().getCommandObj();
        final Class<?> aggregateRootClass = CommandUtil.getAggregateClass(commandObj.getClass());
        // 添加默认实现
        if (commandObj instanceof DeleteCommand) {
            // 默认删除
            ExecutingContextHolder.current().addAttribute(ExecutingContext.DELETE_AGGREGATE_ROOT_OBJ, aggregateCommand.getAggregateRootObj());
        }
        // 执行业务逻辑
        final Method handleBusinessAnnotationMethod = CommandUtil.getHandleBusinessAnnotationMethod(commandObj.getClass());
        if (handleBusinessAnnotationMethod != null) {
            try {
                ReflectUtil.invoke(commandObj, handleBusinessAnnotationMethod, aggregateRootObj);
            } catch (Throwable throwable) {
                log.error("执行命令异常：{}",ExceptionUtil.stacktraceToString(throwable));
                ExceptionUtil.wrapAndThrow(ExceptionUtil.getRootCause(throwable));
            }
        }
        for (CommandWrapper commandWrapper : aggregateCommand.getExtraCommandObjs()) {
            final Object extraCommandObj = commandWrapper.getCommandObj();
            final Method extraHandleBusinessAnnotationMethod = CommandUtil.getHandleBusinessAnnotationMethod(extraCommandObj.getClass());
            if (extraHandleBusinessAnnotationMethod != null) {
                try {
                    ReflectUtil.invoke(extraCommandObj, extraHandleBusinessAnnotationMethod, aggregateRootObj);
                } catch (Throwable obtainException) {
                    ExceptionUtil.wrapAndThrow(ExceptionUtil.getRootCause(obtainException));
                }

            }
        }
        return aggregateRootClass;
    }

    private Object obtainAggregateRootObj(AggregateCommand aggregateCommand) {
        final CommandWrapper aggregateRootCommand = aggregateCommand.getAggregateRootCommand();
        Object aggregateRootObj = null;
        // 获取聚合根
        final Object commandObj = aggregateRootCommand.getCommandObj();
        final Class<?> aggregateRootClass = CommandUtil.getAggregateClass(commandObj.getClass());
        if (aggregateRootClass == null) {
            throw new RuntimeException("找不到操作的聚合根类型");
        }
        final Method obtainAggregateRootObjMethod = CommandUtil.getObtainAggregateObjectAnnotationMethod(commandObj.getClass());
        if (obtainAggregateRootObjMethod != null) {
            // 异常信息
            try {
                aggregateRootObj = ReflectUtil.invoke(commandObj, obtainAggregateRootObjMethod);
            } catch (Throwable obtainException) {
                ExceptionUtil.wrapAndThrow(ExceptionUtil.getRootCause(obtainException));
            }
        } else {
            // loader 获取
            final DomainDefine domainDefine = aggregateRootCommand.getCommandDefine().getDomainDefine();
            if (domainDefine instanceof AggregateRootDefine aggregateRootDefine) {
                //
                final Field businessIdVoucherField = CommandUtil.getBusinessIdVoucherField(commandObj.getClass());
                if (businessIdVoucherField != null) {
                    String businessName = CommandUtil.getBusinessIdVoucherName(businessIdVoucherField);
                    final AggregateRootLoader<Object, Object> aggregateRootLoader = aggregateRootDefine.getAggregateRootLoader(businessName);
                    if (aggregateRootLoader != null) {
                        aggregateRootObj = aggregateRootLoader.loadAggregateRoot(ReflectUtil.getFieldValue(commandObj, businessIdVoucherField));
                    }
                }
            }
        }

        if (aggregateRootObj == null) {
            throw new RuntimeException("找不到聚合根对象");
        }
        // 尝试转换一次，obtainPostProcess
        final List<AggregateObjectPostProcessor> obtainAggregateObjectPostProcessors = aggregateObjectPostProcessorRegistrar.matchAggregateObjectPostProcessors(aggregateRootClass);
        if (CollUtil.isNotEmpty(obtainAggregateObjectPostProcessors)) {
            for (AggregateObjectPostProcessor postProcessor : obtainAggregateObjectPostProcessors) {
                aggregateRootObj = postProcessor.afterObtainAggregateObject(aggregateRootObj);
            }
        }
        if (aggregateRootObj == null) {
            throw new RuntimeException("找不到聚合根对象");
        }
        aggregateCommand.setAggregateRootObj(aggregateRootObj);
        // 获取其他领域对象
        if (aggregateRootObj instanceof ExpandableAggregateRoot<?> expandableAggregateRootObj) {
            // filter
            final List<CommandWrapper> attachedAggregateCommands = aggregateCommand.getExtraCommandObjs().stream().filter(cw -> {
                final Object extraCommandObj = cw.getCommandObj();
                final Class<?> attachedAggregateRoot = CommandUtil.getAttachedAggregateRoot(extraCommandObj.getClass());
                if (attachedAggregateRoot != null) {
                    return true;
                }
                if (!aggregateRootClass.equals(CommandUtil.getAggregateClass(extraCommandObj.getClass()))) {
                    throw new RuntimeException("不是同一个聚合根");
                }
                return false;
            }).toList();
            if (CollUtil.isNotEmpty(attachedAggregateCommands)) {
                for (CommandWrapper attachedAggregateCommand : attachedAggregateCommands) {
                    //
                    final Object attachedCommandObj = attachedAggregateCommand.getCommandObj();
                    final Method obtainExtraObjMethod = CommandUtil.getObtainAggregateObjectAnnotationMethod(attachedCommandObj.getClass());
                    if (obtainExtraObjMethod != null) {
                        Object extraObj = ReflectUtil.invoke(attachedCommandObj, obtainExtraObjMethod, aggregateRootObj);
                        expandableAggregateRootObj.attachObject(extraObj);
                    }
                }
            }
        }
        return aggregateRootObj;
    }


    private Object lookupAggregateId(Object aggregateRootObj) {
        if (aggregateRootObj instanceof BaseEntity<?>) {
            return ((BaseEntity<?>) aggregateRootObj).getId();
        }
        // 默认找id 属性
        final Object idValue = ReflectUtil.getFieldValue(aggregateRootObj, "id");
        if (idValue != null) {
            return idValue;
        }
        throw new RuntimeException("找不到聚合根id");
    }

    /**
     * 转化聚合命令
     *
     * @param commandObjs
     * @return
     */
    private AggregateCommand coverAggregateCommand(Object... commandObjs) {
        final List<Object> commandObjects = new ArrayList<>();
        for (Object commandObj : commandObjs) {
            if (commandObj instanceof List<?> commandObjList) {
                commandObjects.addAll(commandObjList);
            } else if (ArrayUtil.isArray(commandObj)) {
                final Object[] objArray = (Object[]) commandObj;
                commandObjects.addAll(Arrays.asList(objArray));
            } else {
                commandObjects.add(commandObj);
            }
        }
        final Object aggregateRootCommandObj = commandObjects.get(0);
        final String aggregateRootName = aggregateRootCommandObj.getClass().getName();
        final Class<?> aggregateClass = CommandUtil.getAggregateClass(aggregateRootCommandObj.getClass());
        final String aggregateName = DomainUtil.getAggregateName(aggregateClass);
        final CommandWrapper aggregateRootCommand = new CommandWrapper(new CommandDefine(aggregateRootName, domainDefineRegistrar.getByAggregateName(aggregateName)));

        aggregateRootCommand.setCommandObj(aggregateRootCommandObj);
        final AggregateCommand aggregateCommand = new AggregateCommand(aggregateRootCommand);
        for (int i = 1; i < commandObjects.size(); i++) {
            final Object subCommandObj = commandObjects.get(i);

            final CommandDefine commandDefine = new CommandDefine(subCommandObj.getClass().getName(), domainDefineRegistrar.getByAggregateName(aggregateName));
            aggregateCommand.addExtraCommand(new CommandWrapper(commandDefine, subCommandObj));
        }
        return aggregateCommand;
    }
}
